﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.Extensions.EnvironmentAbstractions;

namespace Microsoft.DotNet.Cli;

internal sealed class PerformanceLogManager
{
    internal const string PerfLogDirEnvVar = "DOTNET_PERFLOG_DIR";
    private const string PerfLogRoot = "PerformanceLogs";
    private const int DefaultNumLogsToKeep = 10;

    private readonly IFileSystem _fileSystem;
    private string _perfLogRoot;

    internal static PerformanceLogManager Instance
    {
        get;
        private set;
    }

    internal static void InitializeAndStartCleanup(IFileSystem fileSystem)
    {
        if (Instance == null)
        {
            Instance = new PerformanceLogManager(fileSystem);

            // Check to see if this instance is part of an already running chain of processes.
            string perfLogDir = Env.GetEnvironmentVariable(PerfLogDirEnvVar);
            if (!string.IsNullOrEmpty(perfLogDir))
            {
                // This process has been provided with a log directory, so use it.
                Instance.UseExistingLogDirectory(perfLogDir);
            }
            else
            {
                // This process was not provided with a log root, so make a new one.
                Instance._perfLogRoot = Path.Combine(CliFolderPathCalculator.DotnetUserProfileFolderPath, PerfLogRoot);
                Instance.CreateLogDirectory();

                Task.Factory.StartNew(() =>
                {
                    Instance.CleanupOldLogs();
                });
            }
        }
    }

    internal PerformanceLogManager(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    internal string CurrentLogDirectory { get; private set; }

    private void CreateLogDirectory()
    {
        // Ensure the log root directory exists.
        if (!_fileSystem.Directory.Exists(_perfLogRoot))
        {
            _fileSystem.Directory.CreateDirectory(_perfLogRoot);
        }

        // Create a new perf log directory.
        CurrentLogDirectory = Path.Combine(_perfLogRoot, Guid.NewGuid().ToString("N"));
        _fileSystem.Directory.CreateDirectory(CurrentLogDirectory);
    }

    private void UseExistingLogDirectory(string logDirectory)
    {
        CurrentLogDirectory = logDirectory;
    }

    private void CleanupOldLogs()
    {
        if (_fileSystem.Directory.Exists(_perfLogRoot))
        {
            List<DirectoryInfo> logDirectories = [];
            foreach (string directoryPath in _fileSystem.Directory.EnumerateDirectories(_perfLogRoot))
            {
                logDirectories.Add(new DirectoryInfo(directoryPath));
            }

            // Sort the list.
            logDirectories.Sort(new LogDirectoryComparer());

            // Figure out how many logs to keep.
            int numLogsToKeep;
            string strNumLogsToKeep = Env.GetEnvironmentVariable("DOTNET_PERF_LOG_COUNT");
            if (!int.TryParse(strNumLogsToKeep, out numLogsToKeep))
            {
                numLogsToKeep = DefaultNumLogsToKeep;

                // -1 == keep all logs
                if (numLogsToKeep == -1)
                {
                    numLogsToKeep = int.MaxValue;
                }
            }

            // Skip the first numLogsToKeep elements.
            if (logDirectories.Count > numLogsToKeep)
            {
                // Prune the old logs.
                for (int i = logDirectories.Count - numLogsToKeep - 1; i >= 0; i--)
                {
                    try
                    {
                        logDirectories[i].Delete(true);
                    }
                    catch
                    {
                        // Do nothing if a log can't be deleted.
                        // We'll get another chance next time around.
                    }
                }
            }
        }
    }
}

/// <summary>
/// Used to sort log directories when deciding which ones to delete.
/// </summary>
internal sealed class LogDirectoryComparer : IComparer<DirectoryInfo>
{
    int IComparer<DirectoryInfo>.Compare(DirectoryInfo x, DirectoryInfo y)
    {
        return x.CreationTime.CompareTo(y.CreationTime);
    }
}
